{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Factory Planning II\n",
    "\n",
    "## Objective and Prerequisites\n",
    "\n",
    "Are you up for a major production planning challenge? Try this example where you will learn how to create an optimal production plan that will not only maximize profits, but also determine which month in which to perform maintenance operations on your machines.\n",
    "\n",
    "More information on this type of model can be found in example #4 of the fifth edition of Modeling Building in Mathematical Programming by H. P. Williams on pages 256 and 302 – 303.\n",
    "\n",
    "This modeling example is at the intermediate level, where we assume that you know Python and are familiar with the Gurobi Python API. In addition, you should have some knowledge about building mathematical optimization models.\n",
    "\n",
    "**Download the Repository** <br />\n",
    "You can download the repository containing this and other examples by clicking [here](https://github.com/Gurobi/modeling-examples/archive/master.zip).\n",
    "\n",
    "---\n",
    "## Problem Description\n",
    "\n",
    "A factory makes seven products (Prod 1 to Prod 7) using a range of machines including:\n",
    "\n",
    "- Four grinders\n",
    "- Two vertical drills\n",
    "- Three horizontal drills\n",
    "- One borer\n",
    "- One planer\n",
    "\n",
    "Each product has a defined profit contribution per unit sold (defined as the sales price per unit minus the cost of raw materials). In addition, the manufacturing of each product requires a certain amount of time on each machine (in hours). The profit and manufacturing time value are shown below. A dash indicates that the manufacturing process for the given product does not require that machine.\n",
    "\n",
    "| <i></i> | PROD1 | PROD2 | PROD3 | PROD4 | PROD5 | PROD6 | PROD7 |\n",
    "| --- | --- | --- | --- | --- | --- | --- | --- |\n",
    "| Profit | 10 | 6 | 8 | 4 | 11 | 9 | 3 |\n",
    "| Grinding | 0.5 | 0.7 | - | - | 0.3 | 0.2 | 0.5 |\n",
    "| Vertical Drilling | 0.1 | 0.2 | - | 0.3 | - | 0.6 | - |\n",
    "| Horizontal Drilling | 0.2 | - | 0.8 | - | - | - | 0.6 |\n",
    "| Boring | 0.05 | 0.03 | - | 0.07 | 0.1 | - | 0.08 |\n",
    "| Planning | - | - | 0.01 | - | 0.05 | - | 0.05 |\n",
    "\n",
    "Instead of pre-defining a maintenance schedule for the machines, as was done in Factory Planning I, in this version of the model we will also optimize the maintenance schedule.\n",
    "\n",
    "The maintenance requirements are as follows:\n",
    "\n",
    "- Each machine must be down for maintenance in one month of the six.\n",
    "- The exception to the above are the grinding machines as only two of them need to be down during the six months.\n",
    "\n",
    "There are limitations on how many of each product can be sold in a given month. These limits are shown below:\n",
    "\n",
    "| Month | PROD1 | PROD2 | PROD3 | PROD4 | PROD5 | PROD6 | PROD7 |\n",
    "| --- | --- | --- | --- | --- | --- | --- | --- |\n",
    "| January | 500 | 1000 | 300 | 300 | 800 | 200 | 100 |\n",
    "| February | 600 | 500 | 200 | 0 | 400 | 300 | 150 |\n",
    "| March | 300 | 600 | 0 | 0 | 500 | 400 | 100 |\n",
    "| April | 200 | 300 | 400 | 500 | 200 | 0 | 100 |\n",
    "| May | 0 | 100 | 500 | 100 | 1000 | 300 | 0 |\n",
    "| June | 500 | 500 | 100 | 300 | 1100 | 500 | 60 |\n",
    "\n",
    "Up to 100 units of each product may be stored in inventory at a cost of $0.50 per unit per month. At the start of January, there is no product inventory. However, by the end of June, there should be 50 units of each product in inventory.\n",
    "\n",
    "The factory produces products six days a week using two eight-hour shifts per day. It may be assumed that each month consists of 24 working days. Also, for the purposes of this model, there are no production sequencing issues that need to be taken into account.\n",
    "\n",
    "What should the production and maintenance plans look like? Also, is it possible to recommend any price increases and determine the value of acquiring any new machines?\n",
    "\n",
    "This problem is based on a larger model built for the Cornish engineering company of Holman Brothers.\n",
    "\n",
    "---\n",
    "## Model Formulation\n",
    "\n",
    "### Sets and Indices\n",
    "\n",
    "$t \\in \\text{Months}=\\{\\text{Jan},\\text{Feb},\\text{Mar},\\text{Apr},\\text{May},\\text{Jun}\\}$: Set of months.\n",
    "\n",
    "$p \\in \\text{Products}=\\{1,2,\\dots,7\\}$: Set of products.\n",
    "\n",
    "$m \\in \\text{Machines}=\\{\\text{Grinder},\\text{VertDrill},\\text{horiDrill},\\text{Borer},\\text{Planer}\\}$: Set of machines.\n",
    "\n",
    "### Parameters\n",
    "\n",
    "$\\text{hours_per_month} \\in \\mathbb{R}^+$: Time (in hours/month) available at any machine on a monthly basis. It results from multiplying the number of working days (24 days) by the number of shifts per day (2) by the duration of a shift (8 hours).\n",
    "\n",
    "$\\text{max_inventory} \\in \\mathbb{N}$: Maximum number of units of a single product type that can be stored in inventory at any given month.\n",
    "\n",
    "$\\text{holding_cost} \\in \\mathbb{R}^+$: Monthly cost (in USD/unit/month) of keeping in inventory a unit of any product type.\n",
    "\n",
    "$\\text{store_target} \\in \\mathbb{N}$: Number of units of each product type to keep in inventory at the end of the planning horizon.\n",
    "\n",
    "$\\text{profit}_p \\in \\mathbb{R}^+$: Profit (in USD/unit) of product $p$.\n",
    "\n",
    "$\\text{installed}_m \\in \\mathbb{N}$: Number of machines of type $m$ installed in the factory.\n",
    "\n",
    "$\\text{down_req}_{m} \\in \\mathbb{N}$: Number of machines of type $m$ that should be scheduled for maintenance at some point in the planning horizon.\n",
    "\n",
    "$\\text{time_req}_{m,p} \\in \\mathbb{R}^+$: Time (in hours/unit) needed on machine $m$ to manufacture one unit of product $p$.\n",
    "\n",
    "$\\text{max_sales}_{t,p} \\in \\mathbb{N}$: Maximum number of units of product $p$ that can be sold at month $t$.\n",
    "\n",
    "### Decision Variables\n",
    "\n",
    "$\\text{make}_{t,p} \\in \\mathbb{R}^+$: Number of units of product $p$ to manufacture at month $t$.\n",
    "\n",
    "$\\text{store}_{t,p} \\in [0, \\text{max_inventory}] \\subset \\mathbb{R}^+$: Number of units of product $p$ to store at month $t$.\n",
    "\n",
    "$\\text{sell}_{t,p} \\in [0, \\text{max_sales}_{t,p}] \\subset \\mathbb{R}^+$: Number of units of product $p$ to sell at month $t$.\n",
    "\n",
    "$\\text{repair}_{t,m} \\in \\{0,1,\\dots, \\text{down_req}_m\\} \\subset \\mathbb{N}$: Number of machines of type $m$ scheduled for maintenance at month $t$.\n",
    "\n",
    "**Assumption:** We can produce fractional units.\n",
    "\n",
    "### Objective Function\n",
    "\n",
    "- **Profit:** Maximize the total profit (in USD) of the planning horizon.\n",
    "\n",
    "\\begin{equation}\n",
    "\\text{Maximize} \\quad Z = \\sum_{t \\in \\text{Months}}\\sum_{p \\in \\text{Products}}\n",
    "(\\text{profit}_p*\\text{make}_{t,p} - \\text{holding_cost}*\\text{store}_{t,p})\n",
    "\\tag{0}\n",
    "\\end{equation}\n",
    "\n",
    "### Constraints\n",
    "\n",
    "- **Initial Balance:** For each product $p$, the number of units produced should be equal to the number of units sold plus the number stored (in units of product).\n",
    "\n",
    "\\begin{equation}\n",
    "\\text{make}_{\\text{Jan},p} = \\text{sell}_{\\text{Jan},p} + \\text{store}_{\\text{Jan},p} \\quad \\forall p \\in \\text{Products}\n",
    "\\tag{1}\n",
    "\\end{equation}\n",
    "\n",
    "- **Balance:** For each product $p$, the number of units produced in month $t$ and previously stored should be equal to the number of units sold and stored in that month (in units of product).\n",
    "\n",
    "\\begin{equation}\n",
    "\\text{store}_{t-1,p} + \\text{make}_{t,p} = \\text{sell}_{t,p} + \\text{store}_{t,p} \\quad \\forall (t,p) \\in \\text{Months} \\setminus \\{\\text{Jan}\\} \\times \\text{Products}\n",
    "\\tag{2}\n",
    "\\end{equation}\n",
    "\n",
    "- **Inventory Target:** The number of units of product $p$ kept in inventory at the end of the planning horizon should hit the target (in units of product).\n",
    "\n",
    "\\begin{equation}\n",
    "\\text{store}_{\\text{Jun},p} = \\text{store_target} \\quad \\forall p \\in \\text{Products}\n",
    "\\tag{3}\n",
    "\\end{equation}\n",
    "\n",
    "- **Machine Capacity:** Total time used to manufacture any product at machine type $m$ cannot exceed its monthly capacity (in hours).\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{p \\in \\text{Products}}\\text{time_req}_{m,p}*\\text{make}_{t,p} \\leq \\text{hours_per_month}*(\\text{installed}_m - \\text{repair}_{t,m}) \\quad \\forall (t,m) \\in \\text{Months} \\times \\text{Machines}\n",
    "\\tag{4}\n",
    "\\end{equation}\n",
    "\n",
    "- **Maintenance**: The number of machines of type $m$ scheduled for maintenance should meet the requirement.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{t \\in \\text{Months}}\\text{repair}_{t,m} = \\text{down_req}_m \\quad \\forall m \\in \\text{Machines}\n",
    "\\tag{5}\n",
    "\\end{equation}\n",
    "\n",
    "---\n",
    "## Python Implementation\n",
    "\n",
    "We import the Gurobi Python Module and other Python libraries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install gurobipy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "import gurobipy as gp\n",
    "from gurobipy import GRB\n",
    "\n",
    "# tested with Python 3.11 & Gurobi 11.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Input Data\n",
    "We define all the input data of the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Parameters\n",
    "\n",
    "products = [\"Prod1\", \"Prod2\", \"Prod3\", \"Prod4\", \"Prod5\", \"Prod6\", \"Prod7\"]\n",
    "machines = [\"grinder\", \"vertDrill\", \"horiDrill\", \"borer\", \"planer\"]\n",
    "months = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\"]\n",
    "\n",
    "profit = {\"Prod1\":10, \"Prod2\":6, \"Prod3\":8, \"Prod4\":4, \"Prod5\":11, \"Prod6\":9, \"Prod7\":3}\n",
    "\n",
    "time_req = {\n",
    "    \"grinder\": {    \"Prod1\": 0.5, \"Prod2\": 0.7, \"Prod5\": 0.3,\n",
    "                    \"Prod6\": 0.2, \"Prod7\": 0.5 },\n",
    "    \"vertDrill\": {  \"Prod1\": 0.1, \"Prod2\": 0.2, \"Prod4\": 0.3,\n",
    "                    \"Prod6\": 0.6 },\n",
    "    \"horiDrill\": {  \"Prod1\": 0.2, \"Prod3\": 0.8, \"Prod7\": 0.6 },\n",
    "    \"borer\": {      \"Prod1\": 0.05,\"Prod2\": 0.03,\"Prod4\": 0.07,\n",
    "                    \"Prod5\": 0.1, \"Prod7\": 0.08 },\n",
    "    \"planer\": {     \"Prod3\": 0.01,\"Prod5\": 0.05,\"Prod7\": 0.05 }\n",
    "}\n",
    "\n",
    "# number of each machine available\n",
    "installed = {\"grinder\":4, \"vertDrill\":2, \"horiDrill\":3, \"borer\":1, \"planer\":1} \n",
    "\n",
    "# number of machines that need to be under maintenance\n",
    "down_req = {\"grinder\":2, \"vertDrill\":2, \"horiDrill\":3, \"borer\":1, \"planer\":1} \n",
    "\n",
    "# market limitation of sells\n",
    "max_sales = {\n",
    "    (\"Jan\", \"Prod1\") : 500,\n",
    "    (\"Jan\", \"Prod2\") : 1000,\n",
    "    (\"Jan\", \"Prod3\") : 300,\n",
    "    (\"Jan\", \"Prod4\") : 300,\n",
    "    (\"Jan\", \"Prod5\") : 800,\n",
    "    (\"Jan\", \"Prod6\") : 200,\n",
    "    (\"Jan\", \"Prod7\") : 100,\n",
    "    (\"Feb\", \"Prod1\") : 600,\n",
    "    (\"Feb\", \"Prod2\") : 500,\n",
    "    (\"Feb\", \"Prod3\") : 200,\n",
    "    (\"Feb\", \"Prod4\") : 0,\n",
    "    (\"Feb\", \"Prod5\") : 400,\n",
    "    (\"Feb\", \"Prod6\") : 300,\n",
    "    (\"Feb\", \"Prod7\") : 150,\n",
    "    (\"Mar\", \"Prod1\") : 300,\n",
    "    (\"Mar\", \"Prod2\") : 600,\n",
    "    (\"Mar\", \"Prod3\") : 0,\n",
    "    (\"Mar\", \"Prod4\") : 0,\n",
    "    (\"Mar\", \"Prod5\") : 500,\n",
    "    (\"Mar\", \"Prod6\") : 400,\n",
    "    (\"Mar\", \"Prod7\") : 100,\n",
    "    (\"Apr\", \"Prod1\") : 200,\n",
    "    (\"Apr\", \"Prod2\") : 300,\n",
    "    (\"Apr\", \"Prod3\") : 400,\n",
    "    (\"Apr\", \"Prod4\") : 500,\n",
    "    (\"Apr\", \"Prod5\") : 200,\n",
    "    (\"Apr\", \"Prod6\") : 0,\n",
    "    (\"Apr\", \"Prod7\") : 100,\n",
    "    (\"May\", \"Prod1\") : 0,\n",
    "    (\"May\", \"Prod2\") : 100,\n",
    "    (\"May\", \"Prod3\") : 500,\n",
    "    (\"May\", \"Prod4\") : 100,\n",
    "    (\"May\", \"Prod5\") : 1000,\n",
    "    (\"May\", \"Prod6\") : 300,\n",
    "    (\"May\", \"Prod7\") : 0,\n",
    "    (\"Jun\", \"Prod1\") : 500,\n",
    "    (\"Jun\", \"Prod2\") : 500,\n",
    "    (\"Jun\", \"Prod3\") : 100,\n",
    "    (\"Jun\", \"Prod4\") : 300,\n",
    "    (\"Jun\", \"Prod5\") : 1100,\n",
    "    (\"Jun\", \"Prod6\") : 500,\n",
    "    (\"Jun\", \"Prod7\") : 60,\n",
    "}\n",
    "\n",
    "holding_cost = 0.5\n",
    "max_inventory = 100\n",
    "store_target = 50\n",
    "hours_per_month = 2*8*24"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Deployment\n",
    "We create a model and the variables. We set the UpdateMode parameter to 1 (which simplifies the code – see the documentation for more details). For each product (seven kinds of products) and each time period (month), we will create variables for the amount of which products will get manufactured, held, and sold. In each month, there is an upper limit on the amount of each product that can be sold. This is due to market limitations. For each type of machine and each month we create a variable d, which tells us how many machines are down in this month of this type."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using license file c:\\gurobi\\gurobi.lic\n"
     ]
    }
   ],
   "source": [
    "factory = gp.Model('Factory Planning II')\n",
    "\n",
    "make = factory.addVars(months, products, name=\"Make\") # quantity manufactured\n",
    "store = factory.addVars(months, products, ub=max_inventory, name=\"Store\") # quantity stored\n",
    "sell = factory.addVars(months, products, ub=max_sales, name=\"Sell\") # quantity sold\n",
    "repair = factory.addVars(months, machines, vtype=GRB.INTEGER, ub=down_req, name=\"Repair\") # number of machines down"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we insert the constraints.\n",
    "The balance constraints ensure that the amount of product that is in the storage in the prior month and the amount that gets manufactured equals the amount that is sold and held for each product in the current month. This ensures that all products in the model are manufactured in some month. The initial storage is empty."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "#1. Initial Balance\n",
    "Balance0 = factory.addConstrs((make[months[0], product] == sell[months[0], product] \n",
    "                  + store[months[0], product] for product in products), name=\"Initial_Balance\")\n",
    "    \n",
    "#2. Balance\n",
    "Balance = factory.addConstrs((store[months[months.index(month) -1], product] + \n",
    "                make[month, product] == sell[month, product] + store[month, product] \n",
    "                for product in products for month in months \n",
    "                if month != months[0]), name=\"Balance\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The endstore constraints force that at the end of the last month the storage contains the specified amount of each product."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "#3. Inventory Target\n",
    "TargetInv = factory.addConstrs((store[months[-1], product] == store_target for product in products),  name=\"End_Balance\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The capacity constraints ensure that for each month the time all products require on a certain kind of machine is lower or equal than the available hours for that machine in that month multiplied by the number of available machines in that month. Each product requires some machine hours on different machines. Each machine is down in one or more months due to maintenance, so the number and types of available machines varies per month. There can be multiple machines per machine type."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "#4. Machine Capacity\n",
    "MachineCap = factory.addConstrs((gp.quicksum(time_req[machine][product] * make[month, product]\n",
    "                             for product in time_req[machine])\n",
    "                    <= hours_per_month * (installed[machine] - repair[month, machine])\n",
    "                    for machine in machines for month in months),\n",
    "                   name = \"Capacity\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The maintenance constraints ensure that the specified number and types of machines are down due maintenance in some month. Which month a machine is down is now part of the optimization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "#5. Maintenance\n",
    "\n",
    "Maintenance = factory.addConstrs((repair.sum('*', machine) == down_req[machine] for machine in machines), \"Maintenance\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The objective is to maximize the profit of the company, which consists of the profit for each product minus cost for storing the unsold products. This can be stated as:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "#0. Objective Function\n",
    "obj = gp.quicksum(profit[product] * sell[month, product] -  holding_cost * store[month, product]  \n",
    "               for month in months for product in products)\n",
    "\n",
    "factory.setObjective(obj, GRB.MAXIMIZE)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we start the optimization and Gurobi finds the optimal solution."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (win64)\n",
      "Thread count: 4 physical cores, 8 logical processors, using up to 8 threads\n",
      "Optimize a model with 84 rows, 156 columns and 348 nonzeros\n",
      "Model fingerprint: 0x3c8ea5bf\n",
      "Variable types: 126 continuous, 30 integer (0 binary)\n",
      "Coefficient statistics:\n",
      "  Matrix range     [1e-02, 4e+02]\n",
      "  Objective range  [5e-01, 1e+01]\n",
      "  Bounds range     [6e+01, 1e+03]\n",
      "  RHS range        [1e+00, 2e+03]\n",
      "Found heuristic solution: objective -175.0000000\n",
      "Presolve removed 22 rows and 27 columns\n",
      "Presolve time: 0.00s\n",
      "Presolved: 62 rows, 129 columns, 278 nonzeros\n",
      "Variable types: 105 continuous, 24 integer (12 binary)\n",
      "\n",
      "Root relaxation: objective 1.164550e+05, 13 iterations, 0.00 seconds\n",
      "\n",
      "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
      " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
      "\n",
      "     0     0 116455.000    0   11 -175.00000 116455.000      -     -    0s\n",
      "H    0     0                    92755.000000 116455.000  25.6%     -    0s\n",
      "H    0     0                    95975.000000 116455.000  21.3%     -    0s\n",
      "H    0     0                    108855.00000 116455.000  6.98%     -    0s\n",
      "     0     0     cutoff    0      108855.000 108855.000  0.00%     -    0s\n",
      "\n",
      "Cutting planes:\n",
      "  Gomory: 6\n",
      "  MIR: 3\n",
      "  Relax-and-lift: 12\n",
      "\n",
      "Explored 1 nodes (25 simplex iterations) in 0.03 seconds\n",
      "Thread count was 8 (of 8 available processors)\n",
      "\n",
      "Solution count 4: 108855 95975 92755 -175 \n",
      "\n",
      "Optimal solution found (tolerance 1.00e-04)\n",
      "Best objective 1.088550000000e+05, best bound 1.088550000000e+05, gap 0.0000%\n"
     ]
    }
   ],
   "source": [
    "factory.optimize()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Analysis\n",
    "\n",
    "The result of the optimization model shows that the maximum profit we can achieve is $\\$108,855.00$. This is an increase of $\\$15,139.82$ over the course of six months compared to the Factory Planning I example as a result of being able to pick the maintenance schedule as opposed to having a fixed one. Let's see the solution that achieves that optimal result. \n",
    "\n",
    "### Production Plan\n",
    "This plan determines the amount of each product to make at each period of the planning horizon. For example, in February we make 600 units of product Prod1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Prod1</th>\n",
       "      <th>Prod2</th>\n",
       "      <th>Prod3</th>\n",
       "      <th>Prod4</th>\n",
       "      <th>Prod5</th>\n",
       "      <th>Prod6</th>\n",
       "      <th>Prod7</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Jan</th>\n",
       "      <td>500.0</td>\n",
       "      <td>1000.0</td>\n",
       "      <td>300.0</td>\n",
       "      <td>300.0</td>\n",
       "      <td>800.0</td>\n",
       "      <td>200.0</td>\n",
       "      <td>100.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Feb</th>\n",
       "      <td>600.0</td>\n",
       "      <td>500.0</td>\n",
       "      <td>200.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>400.0</td>\n",
       "      <td>300.0</td>\n",
       "      <td>150.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Mar</th>\n",
       "      <td>400.0</td>\n",
       "      <td>700.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>600.0</td>\n",
       "      <td>400.0</td>\n",
       "      <td>200.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Apr</th>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>May</th>\n",
       "      <td>0.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>500.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>1000.0</td>\n",
       "      <td>300.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Jun</th>\n",
       "      <td>550.0</td>\n",
       "      <td>550.0</td>\n",
       "      <td>150.0</td>\n",
       "      <td>350.0</td>\n",
       "      <td>1150.0</td>\n",
       "      <td>550.0</td>\n",
       "      <td>110.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "     Prod1   Prod2  Prod3  Prod4   Prod5  Prod6  Prod7\n",
       "Jan  500.0  1000.0  300.0  300.0   800.0  200.0  100.0\n",
       "Feb  600.0   500.0  200.0    0.0   400.0  300.0  150.0\n",
       "Mar  400.0   700.0  100.0  100.0   600.0  400.0  200.0\n",
       "Apr    0.0     0.0    0.0    0.0     0.0    0.0    0.0\n",
       "May    0.0   100.0  500.0  100.0  1000.0  300.0    0.0\n",
       "Jun  550.0   550.0  150.0  350.0  1150.0  550.0  110.0"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rows = months.copy()\n",
    "columns = products.copy()\n",
    "make_plan = pd.DataFrame(columns=columns, index=rows, data=0.0)\n",
    "\n",
    "for month, product in make.keys():\n",
    "    if (abs(make[month, product].x) > 1e-6):\n",
    "        make_plan.loc[month, product] = np.round(make[month, product].x, 1)\n",
    "make_plan"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sales Plan\n",
    "This plan defines the amount of each product to sell at each period of the planning horizon. For example, in February we sell 600 units of product Prod1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Prod1</th>\n",
       "      <th>Prod2</th>\n",
       "      <th>Prod3</th>\n",
       "      <th>Prod4</th>\n",
       "      <th>Prod5</th>\n",
       "      <th>Prod6</th>\n",
       "      <th>Prod7</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Jan</th>\n",
       "      <td>500.0</td>\n",
       "      <td>1000.0</td>\n",
       "      <td>300.0</td>\n",
       "      <td>300.0</td>\n",
       "      <td>800.0</td>\n",
       "      <td>200.0</td>\n",
       "      <td>100.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Feb</th>\n",
       "      <td>600.0</td>\n",
       "      <td>500.0</td>\n",
       "      <td>200.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>400.0</td>\n",
       "      <td>300.0</td>\n",
       "      <td>150.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Mar</th>\n",
       "      <td>300.0</td>\n",
       "      <td>600.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>500.0</td>\n",
       "      <td>400.0</td>\n",
       "      <td>100.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Apr</th>\n",
       "      <td>100.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>100.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>May</th>\n",
       "      <td>0.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>500.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>1000.0</td>\n",
       "      <td>300.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Jun</th>\n",
       "      <td>500.0</td>\n",
       "      <td>500.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>300.0</td>\n",
       "      <td>1100.0</td>\n",
       "      <td>500.0</td>\n",
       "      <td>60.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "     Prod1   Prod2  Prod3  Prod4   Prod5  Prod6  Prod7\n",
       "Jan  500.0  1000.0  300.0  300.0   800.0  200.0  100.0\n",
       "Feb  600.0   500.0  200.0    0.0   400.0  300.0  150.0\n",
       "Mar  300.0   600.0    0.0    0.0   500.0  400.0  100.0\n",
       "Apr  100.0   100.0  100.0  100.0   100.0    0.0  100.0\n",
       "May    0.0   100.0  500.0  100.0  1000.0  300.0    0.0\n",
       "Jun  500.0   500.0  100.0  300.0  1100.0  500.0   60.0"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rows = months.copy()\n",
    "columns = products.copy()\n",
    "sell_plan = pd.DataFrame(columns=columns, index=rows, data=0.0)\n",
    "\n",
    "for month, product in sell.keys():\n",
    "    if (abs(sell[month, product].x) > 1e-6):\n",
    "        sell_plan.loc[month, product] = np.round(sell[month, product].x, 1)\n",
    "sell_plan"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Inventory Plan\n",
    "This plan reflects the amount of product in inventory at the end of each period of the planning horizon. For example, at the end of February we have zero units of Prod1 in inventory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Prod1</th>\n",
       "      <th>Prod2</th>\n",
       "      <th>Prod3</th>\n",
       "      <th>Prod4</th>\n",
       "      <th>Prod5</th>\n",
       "      <th>Prod6</th>\n",
       "      <th>Prod7</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Jan</th>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Feb</th>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Mar</th>\n",
       "      <td>100.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>100.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Apr</th>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>May</th>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Jun</th>\n",
       "      <td>50.0</td>\n",
       "      <td>50.0</td>\n",
       "      <td>50.0</td>\n",
       "      <td>50.0</td>\n",
       "      <td>50.0</td>\n",
       "      <td>50.0</td>\n",
       "      <td>50.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "     Prod1  Prod2  Prod3  Prod4  Prod5  Prod6  Prod7\n",
       "Jan    0.0    0.0    0.0    0.0    0.0    0.0    0.0\n",
       "Feb    0.0    0.0    0.0    0.0    0.0    0.0    0.0\n",
       "Mar  100.0  100.0  100.0  100.0  100.0    0.0  100.0\n",
       "Apr    0.0    0.0    0.0    0.0    0.0    0.0    0.0\n",
       "May    0.0    0.0    0.0    0.0    0.0    0.0    0.0\n",
       "Jun   50.0   50.0   50.0   50.0   50.0   50.0   50.0"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rows = months.copy()\n",
    "columns = products.copy()\n",
    "store_plan = pd.DataFrame(columns=columns, index=rows, data=0.0)\n",
    "\n",
    "for month, product in store.keys():\n",
    "    if (abs(store[month, product].x) > 1e-6):\n",
    "        store_plan.loc[month, product] = np.round(store[month, product].x, 1)\n",
    "store_plan"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Maintenance Plan\n",
    "This plan shows the maintenance plan for each period of the planning horizon. For example, 2 machines of type grinder will be down for maintenance in April."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>grinder</th>\n",
       "      <th>vertDrill</th>\n",
       "      <th>horiDrill</th>\n",
       "      <th>borer</th>\n",
       "      <th>planer</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Jan</th>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Feb</th>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Mar</th>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Apr</th>\n",
       "      <td>2.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>May</th>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Jun</th>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>2.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "     grinder  vertDrill  horiDrill  borer  planer\n",
       "Jan      0.0        0.0        1.0    0.0     0.0\n",
       "Feb      0.0        1.0        0.0    0.0     0.0\n",
       "Mar      0.0        0.0        0.0    0.0     0.0\n",
       "Apr      2.0        0.0        0.0    1.0     1.0\n",
       "May      0.0        1.0        0.0    0.0     0.0\n",
       "Jun      0.0        0.0        2.0    0.0     0.0"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rows = months.copy()\n",
    "columns = machines.copy()\n",
    "repair_plan = pd.DataFrame(columns=columns, index=rows, data=0.0)\n",
    "\n",
    "for month, machine in repair.keys():\n",
    "    if (abs(repair[month, machine].x) > 1e-6):\n",
    "        repair_plan.loc[month, machine] = repair[month, machine].x\n",
    "repair_plan"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Note:** If you want to write your solution to a file, rather than print it to the terminal, you can use the model.write() command. An example implementation is:\n",
    "\n",
    "`factory.write(\"factory-planning-2-output.sol\")`\n",
    "\n",
    "---\n",
    "## References\n",
    "\n",
    "H. Paul Williams, Model Building in Mathematical Programming, fifth edition.\n",
    "\n",
    "Copyright &copy; 2020 Gurobi Optimization, LLC"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
